package com.coldwindblows.blog.utils;

import java.awt.Image;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.commonmark.Extension;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import com.coldwindblows.blog.constant.WebConst;
import com.coldwindblows.blog.controller.admin.AttachController;
import com.coldwindblows.blog.exception.TipException;
import com.coldwindblows.blog.pojo.UserDo;

/**
 * Tale工具类
 * Created by ColdWindBlows on 2018/07/01
 *
 */
public class TaleUtils {
	private static final Logger LOGGER = LoggerFactory.getLogger(TaleUtils.class);
	/**
	 * 一个月的秒数
	 */
	//private static final int ONE_MONTH = 30 * 24 * 60 * 60;
	/**
	 * 匹配邮箱正则
	 */
	private static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
	private static final Pattern SLUG_REGEX = Pattern.compile("^[A-Za-z0-9_-]{5,100}$", Pattern.CASE_INSENSITIVE);
	// 使用双重检查锁的单例方式需要添加 volatile 关键字
	private static volatile DataSource newDataSource;
	/**
	 * markdown解析器
	 */
	//private static Parser parser = Parser.builder().build();
	/**
	 * 获取文件所在目录
	 */
	//private static String location = TaleUtils.class.getClassLoader().getResource("").getPath();

	/**
	 * 判断是否是邮箱
	 * @param emailStr
	 * @return
	 */
	public static boolean isEmail(String emailStr) {
		Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(emailStr);
		return matcher.find();
	}
	
	/**
	 * 获取jar外部的文件
	 * @param fileName
	 * @return
	 */
	private static Properties getPropFromFile(String fileName) {
		Properties properties = new Properties();
		try {
			InputStream resourceAsStream = new FileInputStream(fileName);
			properties.load(resourceAsStream);
		} catch(TipException | IOException e) {
			LOGGER.error("get properties file fail={}", e.getMessage());
		}
		
		return properties;
	}
	
	/**
	 * md5加密
	 * @param source
	 * @return
	 */
	public static String MD5encode(String source) {
		if(StringUtils.isBlank(source)) {
			return null;
		}
		MessageDigest messageDigest = null;
		try {
			messageDigest = MessageDigest.getInstance("MD5");
		} catch(NoSuchAlgorithmException ignored) {
			
		}
		byte[] encode = messageDigest.digest(source.getBytes());
		StringBuilder hexString = new StringBuilder();
		String hex = null;
		
		for(byte anEncode : encode) {
			hex = Integer.toHexString(0xff & anEncode);
			if(hex.length() == 1) {
				hexString.append('0');
			}
			hexString.append(hex);
		}
		
		return hexString.toString();
	}
	
	/**
	 * 获取新的数据源
	 * @return
	 */
	public static DataSource getNewDataSource() {
		if(newDataSource != null) {
			return newDataSource;
		}
		
		synchronized (TaleUtils.class) {
			if(newDataSource != null) {
				return newDataSource;
			}
			Properties properties = TaleUtils.getPropFromFile("application-default.properties");
			if(properties.size() == 0) {
				return newDataSource;
			}
			DriverManagerDataSource managerDataSource = new DriverManagerDataSource();
			managerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
			managerDataSource.setPassword(properties.getProperty("spring.datasource.password"));
			String str = "jdbc:mysql://" + properties.getProperty("spring.datasource.url") + "/" + properties.getProperty("spring.datasource.dbname") + "?useUnicode=true&characterEncoding=utf-8&useSSL=false";
			managerDataSource.setUrl(str);
			managerDataSource.setUsername(properties.getProperty("spring.datasource.username"));
			newDataSource = managerDataSource;
		}
		
		return newDataSource;
	}
	
	/**
	 * 返回当前登录用户
	 * @param request
	 * @return
	 */
	public static UserDo getLoginUser(HttpServletRequest request) {
		HttpSession session = request.getSession();
		if(null == session) {
			return null;
		}
		
		return (UserDo)session.getAttribute(WebConst.LOGIN_SESSION_KEY);
	}
	
	/**
	 * 获取cookie中的用户id
	 * @param request
	 * @return
	 */
	public static Integer getCookieUid(HttpServletRequest request) {
		if(null == request) {
			return null;
		}
		
		Cookie cookie = cookieRaw(WebConst.USER_IN_COOKIE, request);
		
		if(cookie == null || cookie.getValue() == null) {
			return null;
		}
		
		try {
			String uid = Tools.deAes(cookie.getValue(), WebConst.AES_SALT);
			return StringUtils.isNoneBlank(uid) && Tools.isNumber(uid) ? Integer.valueOf(uid) : null;
		} catch(Exception e) {
			
		}
		
		return null;
	}
	
	/**
	 * 根据名称获取指定cookie
	 * @param name 名称
	 * @param request 请求
	 * @return
	 */
	private static Cookie cookieRaw(String name, HttpServletRequest request) {
		Cookie[] servletCookies = request.getCookies();
		if(null == servletCookies) {
			return null;
		}
		for(Cookie c : servletCookies) {
			if(name.equals(c.getName())) {
				return c;
			}
		}
		
		return null;
	}
	
	/**
	 * 设置记住密码cookie
	 * @param response
	 * @param uid
	 */
	public static void setCookie(HttpServletResponse response, Integer uid) {
		try {
			String val = Tools.enAes(uid.toString(), WebConst.AES_SALT);
			boolean isSSL = false;
			Cookie cookie = new Cookie(WebConst.USER_IN_COOKIE, val);
			cookie.setPath("/");
			cookie.setMaxAge(60 * 30);
			cookie.setSecure(isSSL);
			response.addCookie(cookie);
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 提取html中的文字
	 * @param html
	 * @return
	 */
	public static String htmlToText(String html) {
		if(StringUtils.isNotBlank(html)) {
			return html.replaceAll("(?s)<[^>]*>(\\s*<[^>]*>)*", " ");
		}
		return "";
	}
	
	/**
	 * markdown转换为html
	 * @param markdown
	 * @return
	 */
	public static String mdToHtml(String markdown) {
		if(StringUtils.isBlank(markdown)) {
			return "";
		}
		List<Extension> extensions = Arrays.asList(TablesExtension.create());
		Parser parser = Parser.builder().extensions(extensions).build();
		Node document = parser.parse(markdown);
		HtmlRenderer renderer = HtmlRenderer.builder().extensions(extensions).build();
		String content = renderer.render(document);
		content = Commons.emoji(content);
		
		return content;
	}
	
	/**
	 * 退出登录状态
	 * @param session
	 * @param response
	 */
	public static void logout(HttpSession session, HttpServletResponse response) {
		session.removeAttribute(WebConst.LOGIN_SESSION_KEY);
		Cookie cookie = new Cookie(WebConst.USER_IN_COOKIE, "");
		cookie.setMaxAge(0);
		response.addCookie(cookie);
		try {
			response.sendRedirect(Commons.site_url());
		} catch(IOException e) {
			LOGGER.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 替换HTML脚本
	 * @param value
	 * @return
	 */
	public static String cleanXSS(String value) {
		value = value.replace("<", "&lt;").replaceAll(">", "&gt;");
		value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");
		value = value.replaceAll("'", "&#39;");
		value = value.replaceAll("eval\\((.*)\\)", "");
		value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
		value = value.replaceAll("script", "");
		
		return value;
	}
	
	/**
	 * 过滤XSS注入
	 * @param value
	 * @return
	 */
	public static String filterXSS(String value) {
		String cleanValue = null;
		if(value == null) {
			return cleanValue;
		}
		
		cleanValue = Normalizer.normalize(value, Normalizer.Form.NFD);
		cleanValue = cleanValue.replaceAll("\0", "");
		
		Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		 
		scriptPattern = Pattern.compile("src[\\r\\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
		cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
		
		return cleanValue;
	}
	
	/**
	 * 判断是否是合法路径
	 * @param slug
	 * @return
	 */
	public static boolean isPath(String slug) {
		if(StringUtils.isBlank(slug)) {
			return false;
		}
		
		if(slug.contains("/") || slug.contains(" ") || slug.contains(".")) {
			return false;
		}
		
		Matcher matcher = SLUG_REGEX.matcher(slug);
		return matcher.find();
	}
	
	public static String getFileKey(String name) {
		String prefix = "/upload/" + TimeUtil.toTime(new Date(), "yyyy/MM");
		if(!new File(AttachController.CLASSPATH + prefix).exists()) {
			new File(AttachController.CLASSPATH + prefix).mkdirs();
		}
		
		name = StringUtils.trimToNull(name);
		if(null == name) {
			return prefix + "/" + UUID.UU32() + "." + null;
		}
		name = name.replace('\\', '/');
		name = name.substring(name.lastIndexOf("/") + 1);
		int index = name.lastIndexOf(".");
		String ext = null;
		if(index >= 0) {
			ext = StringUtils.trimToNull(name.substring(index + 1));
		}
		
		return prefix + "/" + UUID.UU32() + "." + (ext == null ? null : (ext));
	}
	
	/**
	 * 判断文件是否是图片类型
	 * @param imageFile
	 * @return
	 */
	public static boolean isImage(InputStream imageFile) {
		try {
			Image img = ImageIO.read(imageFile);
			if(img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) {
				return false;
			}
			return true;
		} catch(Exception e) {
			return false;
		}
	}
	
	/**
	 * 随机数
	 * @param size 多少位
	 * @return
	 */
	public static String getRandomNumber(int size) {
		String num = "";
		
		for(int i = 0; i < size; i++) {
			double a = Math.random() * 9.0D;
			a = Math.ceil(a);
			int randomNum = (new Double(a)).intValue();
			num = num + randomNum;
		}
		
		return num;
	}
	
	/**
	 * TODO 获取保存文件的位置，jar所在目录的路径, 这个方法可能有问题
	 * @return
	 */
	public static String getUploadFilePath() {
		String path = TaleUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();
		path = path.substring(1);
		
		try {
			path = URLDecoder.decode(path, "UTF-8");
		} catch(UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		int lastIndex = path.lastIndexOf("/") + 1;
		path = path.substring(0, lastIndex);
		File file = new File("");
		
		return file.getAbsolutePath() + "/";
	}
	
	/**
	 * 通过SecureRandom随机获得一个16进制的盐值
	 * @return String
	 */
	public static String getSalt() {
		byte[] bytes = new byte[20];
		SecureRandom sr = null;
		try {
			sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
		} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
			e.printStackTrace();
		}
		sr.nextBytes(bytes);
		StringBuffer sbresult = new StringBuffer();
		for (byte b : bytes) {
			String hex = Integer.toHexString(0xff & b);
			if (hex.length() == 1)
				sbresult.append('0');
			sbresult.append(hex);
		}
		return sbresult.toString();
	}
	
	/**
	 * 比较两个数组是否相等（慢比较（无论哪个位置开始就不相等了，所需的时间都是一样的））
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	private static boolean slowEquals(byte[] a, byte[] b) {
		int diff = a.length ^ b.length;
		for (int i = 0; i < a.length && i < b.length; i++)
			diff |= a[i] ^ b[i];
		return diff == 0;
	}
	
	/**
	 * 比较密码是否相等
	 * @param inputPwd 输入的密码
	 * @param originPwd 用户密码
	 * @return boolean
	 */
	public static boolean pwdCompare(String inputPwd,String originPwd) {
		return slowEquals(inputPwd.getBytes(),originPwd.getBytes());
	}
	
	/**
	 * 将密码加密(SHA512加密)
	 * @param pwd
	 * @param salt
	 * @return String
	 */
	public static String getHashPassword(String salt, String pwd) {
		String strResult = null;
		if(salt == null || salt.isEmpty() || pwd == null || pwd.isEmpty()) {
			return null;
		}
		String saltAndPwd = new StringBuffer(salt).append(pwd).toString();
		try {
			//创建加密对象，传入加密类型
			MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
			//传入要加密的字符串
			messageDigest.update(saltAndPwd.getBytes());
			//得到byte类型结果
			byte byteBuffer[] = messageDigest.digest();
			StringBuffer strHexString = new StringBuffer();
			//将byte类型转换为string
			for(byte b : byteBuffer) {
				String hex = Integer.toHexString(0xff & b);
				if(hex.length() == 1)
					strHexString.append('0');
				strHexString.append(hex);
			}
			//得到返回结果
			strResult = strHexString.toString();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return strResult;
	}
}
